from flask import Flask, render_template, request, redirect, url_for, jsonify, make_response, flash, send_file
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import inspect
import requests, logging, qrcode
from io import BytesIO
from datetime import datetime, timedelta

from bitcoin.wallet import CBitcoinSecret, P2PKHBitcoinAddress
from bitcoin.signmessage import BitcoinMessage, VerifyMessage, SignMessage

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
session = requests.Session()

app = Flask(__name__)
app.config.from_object('config.Config')
logging.debug('dj user is ' + app.config['DJ_USER'])
db = SQLAlchemy(app)

# Event model
class Event(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128), nullable=False)
    thumbnail = db.Column(db.String(256))
    date = db.Column(db.String(20))
    time = db.Column(db.String(10))

class Track(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	title = db.Column(db.String(128), nullable=False)
	date = db.Column(db.String(20))
	filename = db.Column(db.String(128), nullable=False)


#──────────────────────────────────────────────

def _blacklisted(blacklist: list, ip: str):
	if ip in blacklist:
		logging.info(f"banhammer time for {ip}")
		return True
	return False

#────── Check tables and load or init ──────────

def ensure_tables_and_load():
    with app.app_context():
        inspector = inspect(db.engine)
        tables = inspector.get_table_names()

        if 'track' not in tables or 'event' not in tables:
            logging.info("Missing tables — initializing database.")
            init_db_with_seed_data()
        else:
            logging.info("Tables found. Proceeding to load data.")

        events = Event.query.all()
        tracks = Track.query.limit(10).all()

        return events, tracks

#──────────────────────────────────────────────
# get BTC address from cookies 
#──────────────────────────────────────────────

def get_address_from_cookies():
	if request.cookies.get("djdani-holder") and request.cookies.get("djdani-address"):
		return request.cookies.get("djdani-address")


#──────────────────────────────────────────────
# _is_admin()
#──────────────────────────────────────────────

def _is_admin():
	isadmin = False
	thisuser = get_address_from_cookies()
	if thisuser == app.config['ADMIN_USER'] or thisuser == app.config['DJ_USER']:
		isadmin = True
	return isadmin

#──────────────────────────────────────────────
# fetch 10 mp3 tracks
#──────────────────────────────────────────────

def get_tracks(start=0):
    # from models import Track  # or ensure Track is already imported if in the same file
    try:
        tracks = Track.query.offset(start).limit(10).all()
        return [
            {
                "id": track.id,
                "title": track.title,
                "date": track.date,
                "filename": track.filename
            }
            for track in tracks
        ]
    except Exception as e:
        logging.error(f"Error fetching tracks: {e}")
        return []

#──────────────────────────────────────────────
# fetch a list of DJDANI holders addresses
#──────────────────────────────────────────────

def fetch_holders():
    url = 'https://tokenscan.io/api/holders/djdani'
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()  # Raises HTTPError for bad status codes
        data = response.json()
        addresses = [entry['address'] for entry in data.get('data', [])]
        logging.debug(f"Fetched {len(addresses)} DJDANI holder addresses.")
        return addresses
    except requests.RequestException as e:
        logging.error(f"Error fetching DJDANI holders: {e}")
        return []
    except ValueError as e:
        logging.error(f"Error decoding JSON response: {e}")
        return []

#──────────────────────────────────────────────

@app.errorhandler(404)
def not_found(e):
    logging.warning("404 - Page not found")
    return render_template('404.html'), 404

#──────────────────────────────────────────────

@app.route('/')
def index():
	if _blacklisted(BAN_LIST, request.remote_addr):
		return render_template('404.html')
	logging.info(f"request from {request.remote_addr}")
	return render_template('index.html')

#──────────────────────────────────────────────

@app.route('/about')
def about():
	if _blacklisted(BAN_LIST, request.remote_addr):
	    return render_template('404.html')
	return render_template('about.html')

#──────────────────────────────────────────────

@app.route('/events', methods=['GET', 'POST'])
def events():
	if _blacklisted(BAN_LIST, request.remote_addr):
		return render_template('404.html')

	admin = False
	thisuser = get_address_from_cookies()
	if thisuser == app.config['ADMIN_USER'] or thisuser == app.config['DJ_USER']:
		admin = True
		logging.debug(f'DJ User was found: {thisuser}')

	events, tracks = ensure_tables_and_load()	# do we have data?

	if request.method == 'POST':
		address = request.form.get('btc_address')
		message = "admin-auth"
		signature = request.form.get('signature')

		try:
			addr = Address.parse(address)
			if not addr.verify_message(message, signature):
				flash("Signature verification failed.", "danger")
				return redirect(url_for('events'))
		except Exception as e:
			logging.error("Signature error: %s", e)
			flash("Invalid signature.", "danger")
			return redirect(url_for('events'))

		title = request.form.get('title')
		thumbnail = request.form.get('thumbnail')
		date = request.form.get('date')
		time = request.form.get('time')
		event = Event(title=title, thumbnail=thumbnail, date=date, time=time)
		db.session.add(event)
		db.session.commit()
		flash("Event added!", "success")

	events = Event.query.all()
	return render_template('events.html', events=events, admin=admin)

#──────────────────────────────────────────────

@app.route('/events/delete/<int:event_id>')
def delete_event(event_id):
	if _blacklisted(BAN_LIST, request.remote_addr):
		return render_template('404.html')
	if not _is_admin():
		return render_template('404.html')

	event = Event.query.get_or_404(event_id)
	db.session.delete(event)
	db.session.commit()
	flash("Event deleted", "info")
	return redirect(url_for('events'))

#──────────────────────────────────────────────
# display playable links to music
#──────────────────────────────────────────────

@app.route('/mp3', methods=['GET', 'POST'])
def mp3():
	if _blacklisted(BAN_LIST, request.remote_addr):
		return render_template('404.html')

	verified = False
	sigmatch = False
	holder_address = None
	msg = "DJ Dani has great sets"
	mp3_files = []	# should read from database
	admin = False
	events, tracks = ensure_tables_and_load()	# do we have data?

	thisuser = get_address_from_cookies()
	if thisuser == app.config['ADMIN_USER'] or thisuser == app.config['DJ_USER']:
		admin = True
		logging.debug('DJ User was found!')

	if request.cookies.get("djdani-holder"):
		# cookie was previously set, DJDANI holder detected (at least they owned it < 90 days ago)
		verified = True
		if request.cookies.get("djdani-address"):
			holder_address = request.cookies.get("djdani-address")
	elif request.method == 'POST':
		address = request.form.get('address')
		signature = request.form.get('signature')
		message = BitcoinMessage(msg)
		sigmatch = VerifyMessage(address, message, signature)

		if not sigmatch:
			return render_template("mp3.html", error="Signature invalid")
		if address not in fetch_holders():	# get hodler addresses from tokenscan API
			return render_template("mp3.html", error=f"could not identify address {address} in list of DJDANI holders")

		verified = True
		holder_address = address

		response = make_response(redirect(url_for('mp3')))
		response.set_cookie("djdani-holder", str(verified), max_age=30*24*60*60)
		response.set_cookie("djdani-address", address, max_age=30*24*60*60)
		logger.debug(f"djdani-holder and djdani-address cookies were set for {address}")
		return response

	mp3_files = get_tracks(start=0) if verified else []
	return render_template("mp3.html", verified=verified, mp3_files=mp3_files, holder_address=holder_address, message_to_sign=msg)

#──────────────────────────────────────────────

@app.route('/coffee', methods=['GET'])
def coffee():
	if _blacklisted(BAN_LIST, request.remote_addr):
	    return render_template('404.html')

	ipfslink = "https://chocolate-selected-perch-889.mypinata.cloud/ipfs/bafkreicu7uum55wqrm4ooyj3gus6zqmnazleteq24qrlbbckok76mhzwaq"
	token_data = requests.get(ipfslink).json()
	return render_template("coffee.html", token=token_data)

#──────────────────────────────────────────────

@app.route("/qrcode/<string:address>")
def get_qrcode(address):
	if _blacklisted(BAN_LIST, request.remote_addr):
		return render_template('404.html')
	btc_uri = f"bitcoin:{address}?amount=0.00005"
	img = qrcode.make(btc_uri)
	buf = BytesIO()
	img.save(buf)
	buf.seek(0)
	return send_file(buf, mimetype='image/png')

#──────────────────────────────────────────────

def init_db_with_seed_data():
    with app.app_context():
        db.create_all()
        logging.info("Created missing tables.")

        # Optionally seed with default data
        if Event.query.count() == 0:
            events = [
                Event(title="Live Set @ Poseidon", thumbnail="/static/img/events/poseiden-may23.jpg", date="2025-05-23", time="19:00"),
            ]
            db.session.bulk_save_objects(events)
            logging.info("Inserted default events.")

        if Track.query.count() == 0:
            tracks = [
                Track(title="Poseideon deep house set from May", date="2025-05-09", filename="2025-05-09-EDM.mp3"),
            ]
            db.session.bulk_save_objects(tracks)
            logging.info("Inserted default tracks.")

        db.session.commit()

#──────────────────────────────────────────────

BAN_LIST = [
	'13.38.46.147',
	'20.65.201.12',
	'20.163.60.170',
	'35.180.33.5',
	'68.69.186.106',
	'45.135.194.43',
	'45.148.10.42',
	'45.148.10.97',
	'64.62.197.95',
	'65.49.1.180',
	'70.39.90.195',
	'94.26.90.84',
	'102.97.163.107',
	'103.156.92.145',
	'104.23.187.175',
	'139.59.175.227',
	'142.93.230.252',
	'154.83.103.115',
	'176.65.137.147',
	'176.65.149.195',
	'180.163.220.43',
	'180.163.220.45',
	'185.218.84.39',
    '196.251.69.233',
	'194.50.16.252'
]
#   '155.2.190.37',

if __name__ == '__main__':
	logging.basicConfig(level=logging.INFO)
	app.run(debug=True, host='digitaldefense.group', port=8000)
